Keep each timeline message's action row inside its message - #2257
Merged
Conversation
brsbl
force-pushed
the
bb/timeline-actions-row-width-thr_w8uypnhb9x
branch
4 times, most recently
from
August 22, 2026 03:43
d124c9b to
0eec59b
Compare
brsbl
marked this pull request as ready for review
August 22, 2026 04:47
The hover-revealed action row under a timeline message sat in normal flow with no width bound, so it rendered at its natural width regardless of the message above it. A two-letter bubble (51px) carrying three actions (76px) overhung by 25px on desktop; on a touch phone the latest message's inline row overhung a 54px bubble by 46px. Bound the row to the message it belongs to and collapse what does not fit: - Measure the row's slot with a ResizeObserver and keep only the actions that fit; the rest move into a trailing "..." menu. The row is absolutely positioned inside a full-width slot so a wide row can never widen a fit-content message column. - Wrap the user bubble and its row in a sub-column sized by the bubble, so the measured slot is exactly the bubble's width. - On touch, tapping "..." expands the hidden actions in place when the whole set fits the timeline column with room to spare, reaching into the empty gutter beside a narrow bubble. When the column is too tight the anchored popover is used instead, since it scrolls and cannot clip. - Size both menus to their widest label instead of a fixed width, and keep the row revealed while its own menu is open.
The row sat flush with the message's border box, so its outer glyph landed 4px from a bubble's edge — inside the bubble's 12px corner radius, reading as if it hung off the message. On the agent side the same slack pushed the glyph 4px inside the prose edge, indented the other way. Align the outer glyph edge to the message's text edge instead: inset the row by the bubble's padding and border minus the icon's hit-box slack (13px desktop, 11px touch), and pull prose rows out by the slack alone. The slot carries the inset as padding so the measured budget is the text width the row must fit, and the row carries a matching offset because an absolutely positioned child resolves `right` against the padding box.
Tapping Copy in the revealed touch row collapsed the row on the same click, unmounting CopyButton before its check could appear. The inline copy carries no toast either (`useClipboardCopy` defaults `successMessage` to null), so on every message but the most recent — where nothing is inline and the row is just the trigger — copying gave no confirmation at all. The popover this branch replaced kept its own check for two seconds. Dispatch the revealed row's copy through `copyToClipboardWithToast` and confirm on the trigger that replaces the row, matching the popover.
brsbl
force-pushed
the
bb/timeline-actions-row-width-thr_w8uypnhb9x
branch
from
August 22, 2026 05:15
82ad0e5 to
d3671a9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
The hover-revealed action row under each timeline message sat in normal flow with no width bound, so it rendered at its natural width regardless of the message it belongs to. A two-letter bubble of 51px carrying three actions of 76px overhung its message by 25px on desktop. On a touch phone the latest message's inline row overhung a 54px bubble by 46px. Because the row was a sibling of the bubble inside a
w-fitcolumn, a wide row could also widen the column itself. The row was misaligned even where it fitted, because its outer glyph sat 4px from the bubble's edge, inside the bubble's 12px corner radius, so it read as hanging off the message.What changed
The change touches
MessageActionBar.tsxandConversationMessageContent.tsx.The row now fits the message it belongs to. The most icons that fit under the message's width are shown inline, and the rest collapse progressively into a trailing "…" menu. When not even one fits, the row is the "…" alone. On touch, older messages keep their existing "…"-only footer, and the most recent message follows the same rules as desktop.
ResizeObserver, andcomputeMessageActionRowLayoutkeeps the actions that fit and moves the remainder into the menu. The row is absolutely positioned inside a full-width slot, so it can never contribute intrinsic width to a fit-content message column.CopyButtonunmounts before its own check can appear, and the inline copy carries no toast.w-48orw-44, taking the desktop menu from 192px down to 150px, capped so a long plugin label wraps instead of running off a narrow viewport.No contract, wire, or CLI surface changed.
Behavior breakpoints
Every pair below was captured in the real dev app from this checkout, using the same thread, route and window bounds, with the product code as the only variable. The before column comes from merge base
c3b588c8f, and the after column comes from the PR head. Numbers are measured from the DOM, and "overhang" is how far the controls extend past their message.Desktop 1280px, wide bubble
All three actions fit, so none collapse. This is unchanged apart from the alignment.
Desktop 1280px, short bubble of 51px
Before, three actions span 76px, a 25px overhang. After, nothing fits, so the row is the "…" alone.
Desktop, the menu holds what did not fit
Progressive overflow
Eight actions. In a wide column all eight are inline. In a 160px column four fit and the other four move into the menu. This one is a Ladle story, because the real thread has only three actions and so shows either all or none.
Compact width 600px with a mouse, resting and then drawer
The same 25px overhang appears before. After, the menu opens as the shared bottom drawer at this width.
Touch phone 390px, resting
Older messages keep the "…"-only footer. The most recent message follows the desktop rules. Here the latest agent reply spans the column, so its three actions are inline, while the latest user bubble of 54px fits none and shows the "…". Before, that bubble carried 100px of icons, a 46px overhang.
Touch phone, tapping "…" reveals the actions in place
The 358px column fits all three, so they appear in the gutter beside the bubble instead of in a popover.
Touch phone, popover fallback
Eight actions in a 160px column cannot fit, so the popover opens instead, on-screen.
How you verified
pnpm exec turbo run test --filter=@bb/app -- MessageActionBarpasses 28 tests, 11 of them new. They cover the layout math at its boundaries, meaning all inline, progressive collapse, and everything in the menu. They also cover desktop collapse into the "More actions" menu, touch collapse into the popover, reveal-in-place when the column has room, collapse-on-choose, the popover fallback when the column is too tight, and the copy confirmation on the trigger. The new assertions fail against the pre-change component.pnpm exec turbo run test --filter=@bb/app -- "thread/timeline"passes 208 tests.pnpm exec turbo run typecheck --filter=@bb/appis clean, and so areoxlintandoxfmtafter the repository moved to Oxc in Migrate linting and formatting to Oxc #2258.One thing is not verified. An installed PWA was never exercised, because
display-mode: standalonecannot be emulated over CDP. The touch path keys off(pointer: coarse)and(max-width: 767px), and the standalone rule inapp.cssonly sets shell height, which this row does not read.BB-Thread-ID: thr_w8uypnhb9x